﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;

namespace LINQPadLogParser
{
  public sealed class IPAddressWrapper : IFormattable
  {
    private readonly string _value;
    private IPAddress _ip;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public IPAddressWrapper(string value)
    {
      _value = value;
      _ip = null;
    }

    private IPAddress IP
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return _ip ?? (_ip = IPAddress.Parse(_value)); }
    }

    public long Address
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)]
// ReSharper disable once CSharpWarnings::CS0618
      get { return IP.Address; }
    }

    public AddressFamily AddressFamily
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return IP.AddressFamily; }
    }

    public long ScopeId
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return IP.ScopeId; }
    }

    public bool IsIPv6Multicast
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return IP.IsIPv6Multicast; }
    }

    public bool IsIPv6LinkLocal
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return IP.IsIPv6LinkLocal; }
    }

    public bool IsIPv6SiteLocal
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return IP.IsIPv6SiteLocal; }
    }

    public bool IsIPv6Teredo
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return IP.IsIPv6Teredo; }
    }

    public bool IsIPv4MappedToIPv6
    {
      [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return IP.IsIPv4MappedToIPv6; }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public string ToString(string format, IFormatProvider formatProvider)
    {
      return _value;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool Equals(IPAddressWrapper other)
    {
      return string.Equals(_value, other._value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override bool Equals(object obj)
    {
      if (ReferenceEquals(null, obj)) return false;
      if (ReferenceEquals(this, obj)) return true;
      return obj is IPAddressWrapper && Equals((IPAddressWrapper) obj);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override int GetHashCode()
    {
      return (_value != null ? _value.GetHashCode() : 0);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public byte[] GetAddressBytes()
    {
      return IP.GetAddressBytes();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public IPAddress MapToIPv6()
    {
      return IP.MapToIPv6();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public IPAddress MapToIPv4()
    {
      return IP.MapToIPv4();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override string ToString()
    {
      return _value;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator <(IPAddressWrapper ip1, IPAddress ip2)
    {
      return Compare(ip1, ip2) == -1;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator >(IPAddressWrapper ip1, IPAddress ip2)
    {
      return Compare(ip1, ip2) == 1;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator <=(IPAddressWrapper ip1, IPAddress ip2)
    {
      return Compare(ip1, ip2) < 1;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator >=(IPAddressWrapper ip1, IPAddress ip2)
    {
      return Compare(ip1, ip2) > -1;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator ==(IPAddressWrapper ip1, IPAddress ip2)
    {
      return Compare(ip1, ip2) == 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator !=(IPAddressWrapper ip1, IPAddress ip2)
    {
      return Compare(ip1, ip2) != 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator <(IPAddressWrapper ip1, IPAddressWrapper ip2)
    {
      return Compare(ip1, ip2) == -1;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator >(IPAddressWrapper ip1, IPAddressWrapper ip2)
    {
      return Compare(ip1, ip2) == 1;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator <=(IPAddressWrapper ip1, IPAddressWrapper ip2)
    {
      return Compare(ip1, ip2) < 1;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator >=(IPAddressWrapper ip1, IPAddressWrapper ip2)
    {
      return Compare(ip1, ip2) > -1;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator ==(IPAddressWrapper ip1, IPAddressWrapper ip2)
    {
      return Compare(ip1, ip2) == 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool operator !=(IPAddressWrapper ip1, IPAddressWrapper ip2)
    {
      return Compare(ip1, ip2) != 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static int Compare(IPAddressWrapper ip1, IPAddress ip2)
    {
      try
      {
        long ip1A = ip1.Address;
// ReSharper disable once CSharpWarnings::CS0618
        long ip2A = ip2.Address;
        if (ip1A > ip2A)
          return -1;
        if (ip2A > ip1A)
          return 1;
        return 0;
      }
      catch (SocketException)
      {
      }
      byte[] bytes1 = ip1.GetAddressBytes();
      byte[] bytes2 = ip2.GetAddressBytes();
      if (bytes1.Length != bytes2.Length)
      {
        if (bytes1.Length > bytes2.Length)
          return 1;
        return -1;
      }
      for (int i = 0; i < bytes1.Length; i++)
      {
        if (bytes1[i] > bytes2[i])
          return 1;
        if (bytes1[i] < bytes2[i])
          return -1;
      }
      return 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static int Compare(IPAddressWrapper ip1, IPAddressWrapper ip2)
    {
      if (ip1.ToString() == ip2.ToString())
        return 0;
      try
      {
        long ip1A = ip1.Address;
        long ip2A = ip2.Address;
        if (ip1A > ip2A)
          return -1;
        if (ip2A > ip1A)
          return 1;
        return 0;
      }
      catch (SocketException)
      {
      }
      byte[] bytes1 = ip1.GetAddressBytes();
      byte[] bytes2 = ip2.GetAddressBytes();
      if (bytes1.Length != bytes2.Length)
      {
        if (bytes1.Length > bytes2.Length)
          return 1;
        return -1;
      }
      for (int i = 0; i < bytes1.Length; i++)
      {
        if (bytes1[i] > bytes2[i])
          return 1;
        if (bytes1[i] < bytes2[i])
          return -1;
      }
      return 0;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static implicit operator IPAddress(IPAddressWrapper wrapper)
    {
      return wrapper.IP;
    }
  }
}